<?php
declare(strict_types=1);

namespace Framework\Libs;

use Framework\Service;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Table;
use Swoole\WebSocket\Server as WebSocketServer;
use Swoole\Http\Server as HttpServer;
// hook app_use.php

class App
{
    protected Service $server;
    protected WebSocketServer|HttpServer $http_server;
    public array $error_file = [];

    // hook app_start.php
    public function __construct()
    {
        // hook app_construct_start.php
        $this->server = $_ENV['bizan'];
        $this->http_server = $this->server->http_server;
    }

    public function _T($name): Table
    {
        // replace table
    }

    public function error(int $code,bool $is_ajax,Response $response, $message): bool
    {
        // hook app_error_start.php

        if ($is_ajax) {
            $response->status(200);
            $response->header("Content-Type", "application/json");
            $err=[
                'resp_code'=>str_pad($code.'', 4,'0',STR_PAD_LEFT),
                'msg'=>$message,
                'timestamp'=> get_microtime(),
                'data'=>[]
            ];
            $response->end(xn_json_encode($err));
            return false;
        } else {
            $response->status(200);
            $response->header("Content-Type", "text/html");
            $error_file = __APPDIR__ . 'ErrorView/' . $code . '.php';
            if (empty($this->error_file[$code])) {
                if (is_file($error_file)) {
                    $this->error_file[$code] = $error_file;
                } else {
                    $this->error_file[$code] = false;
                }
            }
            if ($this->error_file[$code]) {
                ob_start();
                include _include($this->error_file[$code]);
                $message = ob_get_contents();
                ob_get_clean();
            }
            $response->end($message);
            return false;
        }
    }


    public function initializeController($app, $controller,$route, $request, $response, $ctrl, $action): void
    {
        $app->init($controller,$route,$action, $request, $response);
        !empty($_ENV['_controller_name'][$ctrl][$action]['auth']) && $app->auth();
        !empty($_ENV['_controller_name'][$ctrl][$action]['login']) && $app->login();
    }

    public function run(Request $request, Response $response):void
    {
        $response->header("Access-Control-Allow-Origin", "*");
        $response->header("Access-Control-Allow-Headers", "*");
        $response->header("Access-Control-Allow-Credentials", "true");
        $response->header("Access-Control-Allow-Methods", "HEAD, HEADER, GET, POST, PATCH, PUT, OPTIONS, DELETE");
        $response->header("Access-Control-Max-Age", "86400");
        // hook app_run_start.php
        $is_ajax = false;
        $star_time = get_ut();
        $star_memory = getum();
        $code = 0;
        $message = '';
        $err_message = '';
        $_route='';
        try {
            $request = new \Framework\Support\Request($request);
            $ctrl = $request->ctrl;
            $controller = $request->controller;
            $action = $request->action;
            $route = $request->route;
            $_route = implode('_',$route).'_'.$action;
            $is_ajax = $request->is_ajax;

        } catch (CustomException $e) {

            $code = $e->getCode();
            $message = $e->getMessage();
            $array = $e->getData();
            $this->error($code, $array['is_ajax'], $response, $message);
            return;
        }
        $request->start_memory=$star_memory;
        $request->start_time=$star_time;
        try {



            $is_lock = false;
            $limiting = $_ENV['conf']['limiting'] ?? 0;
            if ($limiting > 0) {
                $num = $this->_T('Current')->get($_route, 'Num');
                $num >= $limiting && $is_lock = true;
                $this->_T('Current')->incr($_route, 'Num', 1);
                $this->_T('Current')->set($_route, ['Time' => bcmul((string)get_ut(), "1000", 0)]);
            }

            $is_error = 0;
            $method = $request->_S('REQUEST_METHOD');
            if ($is_lock === true) {
                $result = '系统繁忙,' . $num . '个任务处理中,请稍候...';
                //后面的锁系统自己解，这里不走到后面解锁
                 $this->error(500, $is_ajax, $response, $result);
                return;
            } else {
                $end='';

                if($request->getMethod()=='OPTIONS'){
                    $response->status(200);
                    $response->header("Use-Tim", ut($star_time));
                    $response->header("Use-Ram", um($star_memory));
                    $response->end('ok');
                    return;
                }
                //$app = new $controller();
                //$app->init($route, $request, $response);
                //!empty($_ENV['_controller_name'][$controller][$action]['auth']) && $app->auth();
                //!empty($_ENV['_controller_name'][$controller][$action]['login']) && $app->login();
                //$_action = 'action_' . $action;
                //$end = $app->$_action();

                // replace controller


                $response->status(200);
                $response->header("Use-Tim", ut($star_time));
                $response->header("Use-Ram", um($star_memory));

                if(is_array($end)){
                    $response->header("Content-Type", "application/json;charset=UTF-8");

                    $response->end(xn_json_encode($end));
                }elseif(!empty($end) && str_starts_with($end,'<xml')){
                    $response->header("Content-Type", "application/xml;charset=UTF-8");
                    $response->end($end);
                }else{
                    $response->header("Content-Type", "text/html;charset=UTF-8");
                    $response->end($end);
                }
            }


        } catch (CustomException $e) {
            $type = $e->getCode();
            $type > 10000 && $type -= 10000;
            $type = intval($type);

            //_dump($type);
            // hook app_run_customexception.php
            switch ($type) {
                case 1000:
                    $response->status(200);
                    $response->header("Use-Tim", ut($star_time));
                    $response->header("Use-Ram", um($star_memory));
                    $response->header("Content-Type", $e->getMessage());
                    $data = $e->getData();
                    $response->end($data['data']);
                    break;
                case 1001:
                    $response->status(200);
                    $response->header("Use-Tim", ut($star_time));
                    $response->header("Use-Ram", um($star_memory));
                    $array = $e->getData();

                    $join = is_array($_ENV['sys']['Mines'][$array['ext']]) ? implode(';', $_ENV['sys']['Mines'][$array['ext']]) : $_ENV['sys']['Mines'][$array['ext']];
                    $response->header('Content-Type', $join);
                    $response->header('Content-Disposition', 'attachment; filename="' . $array['name'] . '";');
                    if ($response->sendfile($array['filename'])) {
                        if (!empty($array['unlink'])) {
                            swoole_timer_after($array['unlink'] * 1000, function () use ($array) {
                                is_file($array['filename']) && unlink($array['filename']);
                            });
                        }
                    }
                    break;
                case 301:
                case 302:
                    $response->header("Use-Tim", ut($star_time));
                    $response->header("Use-Ram", um($star_memory));
                    $url = $e->getData();
                    $response->redirect($url, $type);
                    break;
                case 500:
                case E_ERROR:
                    $response->status(500);
                    $err_message = ' E_ERROR ';
                    break;
                case E_PARSE:
                    $response->status(500);
                    $err_message = ' E_PARSE ';
                    break;
                default:
                    $response->status(200);
                    $response->header("Use-Tim", ut($star_time));
                    $response->header("Use-Ram", um($star_memory));
                    $array = $e->getData();
                    empty($array) && $array=$e->getMessage();
                    if(is_array($array)){
                        $response->header("Content-Type", "application/json;charset=UTF-8");
                        $put_key = _CONF('put_key',"");
                        if($put_key && $request->getMethod()=="PUT"){
                            $response->end(aes_encrypt(xn_json_encode($array),$put_key));
                        }else{
                            $response->end(xn_json_encode($array));
                        }
                    }elseif(!empty($array) && str_starts_with($array,'<xml')){
                        $response->header("Content-Type", "application/xml;charset=UTF-8");
                        $response->end($array);
                    }else{
                        $response->header("Content-Type", "text/html;charset=UTF-8");
                        $response->end($array);
                    }

            }
            // hook app_run_customexception_end.php




        } catch (\Throwable $e) {
            $err_message = ' E_ERROR ';
        }

        if (!empty($err_message)) {
            $type = strtolower(trim($err_message));
            $err_message .= sprintf("\nM：%s \nF：%s \nL：%s\n", $e->getMessage(), $e->getFile(), $e->getLine());
            xn_log($err_message . $e->getTraceAsString(), 'php_' . $type . '_error');

            if (_CONF('debug')) {
                $traces = [];
                do {
                    $traces[] = [
                        'name' => get_class($e),
                        'file' => $e->getFile(),
                        'line' => $e->getLine(),
                        'code' => $e->getCode(),
                        'message' => $this->getMessage($e),
                        'trace' => $e->getTrace(),
                        'source' => $this->getSourceCode($e),
                    ];
                } while ($e = $e->getPrevious());

                $error_data = [
                    'SERVER' => $request->server,
                    'GET' => $request->get(),
                    'POST' => $request->post(),
                    'FILES' => $request->getFiles(),
                    "RawContent" => $request->getContent(),
                ];

                $error_data['SERVER']['FD'] = $request->fd;
                if ($is_ajax) {

                    $data = [
                        'resp_code' => '0500',
                        'error_data' => $error_data,
                        'traces' => $traces
                    ];
                    $response->end(xn_json_encode($data));
                } else {


                    ob_start();
                    include _include(__APPDIR__ . 'ErrorView/Error.php');
                    $result = ob_get_contents();
                    ob_end_clean();
                    $response->end($result);
                }
            } else {
                if ($is_ajax) {
                    $response->end('{"resp_code":"0500","msg":"服务端异常"}');
                } else {
                    $response->end('服务端异常');
                }
            }
        }
        // hook app_run_end.php
        $ms = ms_log($star_time);
        $stat = $this->_T('RequestStat')->get($_route);
        empty($stat) && $stat = [
            'Count' => 0,
            'Ms' => 0,
            'Min' => 0,
            'Max' => 0
        ];
        $this->_T('RequestStat')->set($_route, ['Count' => (int)$stat['Count'] + 1, 'Ms' => (int)$stat['Ms'] + $ms, 'Min' => !empty($stat['Min']) ? min($ms, (int)$stat['Min']) : $ms, 'Max' => max($ms, (int)$stat['Max'])]);
        if ($limiting > 0) {
            $this->_T('Current')->decr($_route, 'Num', 1);
        }
    }


    public function do( $data ): void
    {
        try {
            $data['cmd'] && $this->server->event->extend($data['class'], $data['cmd'], $data['value']);
        } catch (\Throwable $e) {
            xn_log($e->getMessage(), 'task_error');
        }
    }

    public function task($r)
    {
        try {
            $m = 'Task';
            $c = $r['controller'] ? ucfirst($r['controller']) : 'Task\Index';
            $action = $r['action'] ? ucfirst($r['action']) : 'Index';
            $ctrl = '\\' . $m . '\\' . $c;
            $k = $m . '_' . $c;
            $starttime = get_ut();
            if (isset($_ENV['_taks_controller_name'][$ctrl])) {
                // replace taks_controller
                if (isset($_ENV['_taks_controller_name'][$ctrl][$action])) {
                    $k = strtolower($k . '_' . $action);
                    $this->_T('RequestStat')->incr($k, 'Count', 1);
                    $this->_T('RequestStat')->incr($k, 'Ms', ms_log($starttime));
                }
            }
        } catch (\Throwable $e) {
            xn_log($e->getMessage(), 'task_error');
        }
    }


    /**
     * 获取错误信息
     * ErrorException则使用错误级别作为错误编码
     * @access protected
     * @return string                错误信息
     */
    protected function getMessage(\Throwable $exception): string
    {
        $message = $exception->getMessage();
        if (strpos($message, ':')) {
            $name = strstr($message, ':', true);
            $message = $name . strstr($message, ':');
        } elseif (strpos($message, ',')) {
            $name = strstr($message, ',', true);
            $message = $name . ':' . substr(strstr($message, ','), 1);
        }
        return $message;
    }

    /**
     * 获取出错文件内容
     * 获取错误的前9行和后9行
     * @access protected
     * @param \Throwable $exception
     * @return array                 错误文件内容
     */
    protected function getSourceCode(\Throwable $exception): array
    {
        // 读取前9行和后9行
        $line = $exception->getLine();
        $first = ($line - 9 > 0) ? $line - 9 : 1;
        try {
            $contents = file($exception->getFile()) ?: [];
            $source = [
                'first' => $first,
                'source' => array_slice($contents, $first - 1, 19),
            ];
        } catch (\Exception $e) {
            $source = [];
        }
        return $source;
    }
}
?>